#include "gl-debug.hh"
#include "../lib/gl.hh"
#include "../utils/journal.hh"
#include <string>
#include <stdexcept>

namespace ngintoy
{
    namespace utils
    {
        namespace {
            using jn = journal;

#ifdef RELEASE
#else
            std::string source_to_string_KHR(GLenum source)
            {
                switch (source) {
                    case GL_DEBUG_SOURCE_API            : return "(API)";
                    case GL_DEBUG_SOURCE_SHADER_COMPILER: return "(SHADER)";
                    case GL_DEBUG_SOURCE_WINDOW_SYSTEM  : return "(WINDOW)";
                    case GL_DEBUG_SOURCE_THIRD_PARTY    : return "(OTHER)";
                    case GL_DEBUG_SOURCE_APPLICATION    : return "(USER)";
                    case GL_DEBUG_SOURCE_OTHER          : return "(OTHER)";
                    default: throw std::logic_error{"invalid value to source_to_string_KHR"};
                }
            }

            std::string type_to_string_KHR(GLenum type)
            {
                switch (type) {
                    case GL_DEBUG_TYPE_ERROR              : return "<ERROR>";
                    case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: return "<DEPRECATED>";
                    case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR : return "<UNDEFINED>";
                    case GL_DEBUG_TYPE_PERFORMANCE        : return "<PERFORMANCE>";
                    case GL_DEBUG_TYPE_PORTABILITY        : return "<PORTABILITY>";
                    case GL_DEBUG_TYPE_OTHER              : return "<OTHER>";
                    case GL_DEBUG_TYPE_MARKER             : return "<MARKER>";
                    case GL_DEBUG_TYPE_PUSH_GROUP         : return "<PUSH>";
                    case GL_DEBUG_TYPE_POP_GROUP          : return "<POP>";
                    default: throw std::logic_error{"invalid value to type_to_string_KHR"};
                }
            }

            auto severity_to_journal_KHR(GLenum severity)
            {
                switch (severity) {
                    case GL_DEBUG_SEVERITY_HIGH        : return jn::error("debug");
                    case GL_DEBUG_SEVERITY_MEDIUM      : return jn::warn("debug");
                    case GL_DEBUG_SEVERITY_LOW         : return jn::info("debug");
                    case GL_DEBUG_SEVERITY_NOTIFICATION: return jn::info("dinfo");
                    default: throw std::logic_error{"invalid value to severity_to_journal_KHR"};
                }
            }

            std::string source_to_string_ARB(GLenum source)
            {
                switch (source) {
                    case GL_DEBUG_SOURCE_API_ARB            : return "(API)";
                    case GL_DEBUG_SOURCE_SHADER_COMPILER_ARB: return "(SHADER)";
                    case GL_DEBUG_SOURCE_WINDOW_SYSTEM_ARB  : return "(WINDOW)";
                    case GL_DEBUG_SOURCE_THIRD_PARTY_ARB    : return "(OTHER)";
                    case GL_DEBUG_SOURCE_APPLICATION_ARB    : return "(USER)";
                    case GL_DEBUG_SOURCE_OTHER_ARB          : return "(OTHER)";
                    default: throw std::logic_error{"invalid value to source_to_string_ARB"};
                }
            }

            std::string type_to_string_ARB(GLenum type)
            {
                switch (type) {
                    case GL_DEBUG_TYPE_ERROR_ARB              : return "<ERROR>";
                    case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB: return "<DEPRECATED>";
                    case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB : return "<UNDEFINED>";
                    case GL_DEBUG_TYPE_PERFORMANCE_ARB        : return "<PERFORMANCE>";
                    case GL_DEBUG_TYPE_PORTABILITY_ARB        : return "<PORTABILITY>";
                    case GL_DEBUG_TYPE_OTHER_ARB              : return "<OTHER>";
                    default: throw std::logic_error{"invalid value to type_to_string_ARB"};
                }
            }

            auto severity_to_journal_ARB(GLenum severity)
            {
                switch (severity) {
                    case GL_DEBUG_SEVERITY_HIGH  : return jn::error("debug");
                    case GL_DEBUG_SEVERITY_MEDIUM: return jn::warn("debug");
                    case GL_DEBUG_SEVERITY_LOW   : return jn::info("debug");
                    default: throw std::logic_error{"invalid value to severity_to_journal_ARB"};
                }
            }

            void debug_callback_KHR(
                    GLenum source,
                    GLenum type,
                    GLuint id,
                    GLenum severity,
                    GLsizei /*length*/,
                    GLchar const* message,
                    void const* /*userdata*/)
            {
                severity_to_journal_KHR(severity) << "OpenGL: "
                    + source_to_string_KHR(source) + " "
                    + type_to_string_KHR(type) + " "
                    + "[" + std::to_string(id) + "] "
                    + message;

                if (severity == GL_DEBUG_SEVERITY_HIGH)
                    throw std::runtime_error{""};
            }

            void debug_callback_ARB(
                    GLenum source,
                    GLenum type,
                    GLuint id,
                    GLenum severity,
                    GLsizei /*length*/,
                    GLchar const* message,
                    void const* /*userdata*/)
            {
                severity_to_journal_ARB(severity) << "OpenGL: "
                    + source_to_string_ARB(source) + " "
                    + type_to_string_ARB(type) + " "
                    + "[" + std::to_string(id) + "] "
                    + message;

                if (severity == GL_DEBUG_SEVERITY_HIGH_ARB)
                    throw std::runtime_error{""};
            }
#endif
        }

        void gl_debug()
        {
#ifdef RELEASE
            jn::info("debug") << "debug disabled";
#else
            // using KHR_debug
            if (epoxy_has_gl_extension("GL_KHR_debug")) {
                jn::info("debug") << "using KHR_debug";

                // this one is unnecessary in debug context,
                // but let's enable it just to be sure.
                glEnable(GL_DEBUG_OUTPUT);

                glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
                glDebugMessageCallback(&debug_callback_KHR, nullptr);

                return;
            }

            // using ARB_debug_output
            if (epoxy_has_gl_extension("GL_ARB_debug_output")) {
                jn::info("debug") << "using ARB_debug_output";

                glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB);
                glDebugMessageCallbackARB(&debug_callback_ARB, nullptr);

                return;
            }

            jn::warn("debug") << "gl-debug requires either GL_KHR_debug or GL_ARB_debug_output.";
#endif
        }
    }
}

